/* -*-C-*-
********************************************************************************
*
* File:         htmlparse.c
* RCS:          $Id: $
* Description:  Interface to DTD-Driven HTML Parser
* Author:       Niels Mayer
* Created:      1995
* Modified:     Sun Nov 23 21:39:25 1997 (Niels Mayer) npm@mayer.netcom.com
* Language:     C
* Package:      N/A
* Status:       Experimental, do not distribute.
*
* Copyright (C) 1994-1996 Enterprise Integration Technologies Corp. and Niels Mayer.
* WINTERP 1.15-1.99, Copyright (c) 1993, Niels P. Mayer.
* WINTERP 1.0-1.14, Copyright (c) 1989-1992 Hewlett-Packard Co. and Niels Mayer.
* 
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of Enterprise Integration Technologies,
* Hewlett-Packard Company, or Niels Mayer not be used in advertising or
* publicity pertaining to distribution of the software without specific,
* written prior permission. Enterprise Integration Technologies, Hewlett-Packard
* Company, and Niels Mayer makes no representations about the suitability of
* this software for any purpose.  It is provided "as is" without express or
* implied warranty.
* 
* ENTERPRISE INTEGRATION TECHNOLOGIES, HEWLETT-PACKARD COMPANY AND NIELS MAYER
* DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL ENTERPRISE
* INTEGRATION TECHNOLOGIES, HEWLETT-PACKARD COMPANY OR NIELS MAYER BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
* RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
********************************************************************************
*/

#include <stdio.h>
#include <ctype.h>
#include <Xm/Xm.h>
#include "winterp.h"
#include "xlisp/xlobj.h"	/* instance variable numbers for the class 'Class'
				   -- from xlobj.c e.g. IVARTOTAL */

#include "jays-parser/dtd/libdtd/atom.h"
#include "jays-parser/html/html.h"

/* stuff from htmlparseI.c */
extern char err_buf[BUFSIZ];	
extern Block * Htp_Parse_Tree_From_HTML_File(char* parse_file, char* err_filepath);

#define HTML_PARSER_ERROR_FILEPATH "/usr/tmp/htmlparse.err"

/* Instance variable indexes in the HTML Tag Object.
   The order of these indexes corresponds to the
   definition of the html::markup-class object in XLISP. */
enum {				/* note refs to cls-html.lsp def'd classes: */
  TAGOBJ_CHILDREN = 0,		/* idx of ivar 'ivar_children_objs' */
  TAGOBJ_LAST_CHILD,		/* idx of ivar 'ivar_last_child_cons' */
  TAGOBJ_PARENT,		/* idx of ivar 'ivar_parent_obj' */
  TAGOBJ_NAME,			/* idx of ivar 'ivar_name_sym' */
  TAGOBJ_DATA,			/* idx of ivar 'ivar_data' */
  TAGOBJ_ATTRS,			/* idx of ivar 'ivar_attrs_alist' */
  TAGOBJ_FLAGS			/* idx of ivar 'ivar_flags_bitmask' */
  };

/* Instance variable indexes in the HTML Word Object.
   The order of these indexes corresponds to the
   definition of the html-word-image-class object in XLISP. */
enum {				/* note refs to parsewww.lsp-def'd classes: */
  WORDOBJ_STRING = TANGOIMAGEOBJ_SIZE, /* idx of ivar 'ivar_text_str' */
  WORDOBJ_PREV_WORDOBJ,		/* idx of ivar 'ivar_prev' */
  WORDOBJ_NEXT_WORDOBJ,		/* idx of ivar 'ivar_next' */
  WORDOBJ_TAGOBJ,		/* idx of ivar 'ivar_markup_obj' */
  WORDOBJ_NEWLINEP		/* idx of ivar 'ivar_newlinep' */
  };

static LVAL o_HTML_DOCUMENT_CLASS = NULL; /* init'd in Prim_HTML_INIT() */
static LVAL o_HTML_COMMENT_CLASS  = NULL; /* init'd in Prim_HTML_INIT() */
static LVAL o_HTML_DOCTYPE_CLASS  = NULL; /* init'd in Prim_HTML_INIT() */
static LVAL o_HTML_TEXT_CLASS	  = NULL; /* init'd in Prim_HTML_INIT() */
static LVAL o_HTML_TEXTWORD_IMAGE_CLASS = NULL; /* init'd in Prim_HTML_INIT() */
static LVAL o_HTML_TEXTLINE_IMAGE_CLASS = NULL; /* init'd in Prim_HTML_INIT() */
static LVAL o_HTML_DYNAMIC_DATE_CLASS	= NULL; /* init'd in Prim_HTML_INIT() */
static LVAL o_HTML_DYNAMIC_EMAIL_ADDRESS_CLASS = NULL; /* init'd in Prim_HTML_INIT() */
static LVAL o_HTML_DYNAMIC_EXEC_CLASS	= NULL; /* init'd in Prim_HTML_INIT() */
static LVAL o_HTML_DYNAMIC_USER_CLASS	= NULL; /* init'd in Prim_HTML_INIT() */
static LVAL o_HTML_UNKNOWN_TYPE_CLASS	= NULL; /* init'd in Prim_HTML_INIT() */
static LVAL s_HTML_PARSE_ERROR_FILEPATH	= NIL; /* init'd in Prim_HTML_INIT() */
static LVAL s_HTML_COMMENT	= NIL; /* init'd in Prim_HTML_INIT() */
static LVAL s_HTML_TEXT		= NIL; /* init'd in Prim_HTML_INIT() */
static LVAL s_HTML_DOCTYPE	= NIL; /* init'd in Prim_HTML_INIT() */
static LVAL s_HTML_DOCUMENT	= NIL; /* init'd in Prim_HTML_INIT() */
static LVAL s_DYNAMIC_DATE	= NIL; /* init'd in Prim_HTML_INIT() */
static LVAL s_DYNAMIC_EMAIL	= NIL; /* init'd in Prim_HTML_INIT() */
static LVAL s_DYNAMIC_EXEC	= NIL; /* init'd in Prim_HTML_INIT() */
static LVAL s_DYNAMIC_USER	= NIL; /* init'd in Prim_HTML_INIT() */
static LVAL s_UNKNOWN		= NIL; /* init'd in Prim_HTML_INIT() */

static Boolean add_leading_whitespace_on_next_word_p = FALSE;

#ifdef PACKAGES
extern LVAL xlhtmlpack;
#endif /* PACKAGES */

static void Htp_Dump_Tree(Block *top, int depth, LVAL parent_node, Boolean split_text_at_line_p);
extern Block * Htp_Parse_Tree_From_HTML_File(char* parse_file, char* err_filepath);

/* I'm assuming this proc is never called with a NULL or empty string */
static LVAL
Htp_Make_Text_Word_List(char* str)
{
  int i;
  char* wp;
  char* cp;
  LVAL o_word, o_last, o_prev = NIL;

  xlstkcheck(2);
  xlsave(o_word);		/* protect this pointer from GC */
  xlsave(o_last);		/* protect this pointer from GC */

  o_last = o_word =
    newobject(o_HTML_TEXTWORD_IMAGE_CLASS, /* init'd in 'HTML:INIT_PARSER' */
	      getivcnt(o_HTML_TEXTWORD_IMAGE_CLASS, IVARTOTAL));
  
  wp = &(str[0]);

  do {
    cp = &(temptext[0]);
    i = 0;

    while ((*wp == ' ')		/* include leading whitespace ... */
	   && (i < BUFSIZ)) {	/* don't overflow temptext[BUFSIZ] */
      *cp++ = *wp++;
      i++;
      add_leading_whitespace_on_next_word_p = FALSE; /* leading whitespace already added  */
    }
    if (add_leading_whitespace_on_next_word_p) {
      *cp++ = ' ';
      i++;
      add_leading_whitespace_on_next_word_p = FALSE;
    }

    while ((*wp != ' ')		/* include up to next word */
	   && (*wp != '\000')	/* stop at end of string */
	   && (i < BUFSIZ)) {	/* stop if overflowing temptext[BUFSIZ] */
      *cp++ = *wp++;
      i++;
    }

    if (i >= BUFSIZ) {
      temptext[BUFSIZ - 1] = '\000'; /* null terminate end of temptext[] */
      fprintf(stderr,
	      "HTML Parser Warning: truncated input word larger than %d characters ... word='%s'\n",
	      BUFSIZ,
	      temptext);
    }
    else {
      *cp = '\000';		/* null terminate temptext[] */
    }

    if (temptext[0] != '\000') {
      if ((temptext[0] == ' ') && (temptext[1] == '\000')) {
	/* ignore a wordimage-obj containing just one whitespace.  This is
	   an artifact of the current way words are tokenized, which will
	   cause any trailing whitespace at the end of the markup to
	   require the end of the markup's string to contain a single
	   wordimage-obj containing " ". Because this complicates editing,
	   I pass the whitespace on to the beginning of the next word that
	   gets tokenized via 'add_leading_whitespace_on_next_word_p'.
	   Basically, the idea is to have all words be represented the same
	   way, with leading whitespace. the only time this wouldn't happen
	   is for special cases where you have a non-line-breaking markup
	   directly abutting the beginning or end of the given
	   wordimageobj...  (i.e. a case where you don't want any
	   whitespace in the first place). */
	add_leading_whitespace_on_next_word_p = TRUE;
	o_last = NIL;		/* note: this wastes the last newobject()
				   created, it'll get garbage collected
				   eventually */
      }
      else {
	if (o_prev != NIL)
	  setivar(o_prev, WORDOBJ_NEXT_WORDOBJ, o_last);
	setivar(o_last, WORDOBJ_PREV_WORDOBJ, o_prev);
	setivar(o_last, WORDOBJ_STRING, 
		cvstring(temptext)); /* cvstring() allocates a new string here, it is protected from GC because o_last is */
	/* setivar(o_last, WORDOBJ_TAGOBJ, NIL); *//* this now gets set in Htp_Create_HTML_Tag_Inst() called out of Htp_Dump_Tree() */
	o_prev = o_last;
	o_last =
	  newobject(o_HTML_TEXTWORD_IMAGE_CLASS, /* init'd in 'HTML:INIT_PARSER' */
		    getivcnt(o_HTML_TEXTWORD_IMAGE_CLASS, IVARTOTAL));
      }
    }
    else {
      o_last = NIL;		/* note: this wastes the last newobject()
				   created, it'll get garbage collected
				   eventually */
    }
  } while (o_last != NIL);

  xlpopn(2);
  return (o_word);
}

/* I'm assuming this proc is never called with a NULL or empty string */
/* Note that unlike Htp_Make_Text_Word_List(), this one must deal with
   tabs, carriage returns, newlines, etc. */
static LVAL
Htp_Make_Text_Line_List(char* str)
{
  int i, j, spcs;
  char* wp;
  char* cp;
  LVAL o_word, o_last, o_prev = NIL;

  add_leading_whitespace_on_next_word_p = FALSE; /* clear it no matter what */

  xlstkcheck(2);
  xlsave(o_word);		/* protect this pointer from GC */
  xlsave(o_last);		/* protect this pointer from GC */

  o_last = o_word =
    newobject(o_HTML_TEXTLINE_IMAGE_CLASS, /* init'd in 'HTML:INIT_PARSER' */
	      getivcnt(o_HTML_TEXTLINE_IMAGE_CLASS, IVARTOTAL));
  
  wp = &(str[0]);

  do {
    cp = &(temptext[0]);

    if (*wp == '\n')		/* remove a single leading newline ... */
      wp++;
    i = 0;
    while ((*wp != '\n')	/* include up to next line or EOS */
	   && (*wp != '\000')	/* or include up to end of string */
	   && (i < BUFSIZ)) {	/* or while there's space in temptext[BUFSIZ]*/
      switch (*wp) {
      case '\r':		/* carriage return ... */
	wp++;			/* gets skipped */
	break;
      case '\t':		/* tab ... */
	wp++;			/* becomes eight spaces */
	spcs = 8 - (i % 8);
	for (j = 0; (j < spcs); j++) {
	  *cp++ = ' ';
	  i++;
	}
	break;
      default:			/* else copy the char */
	*cp++ = *wp++;
	i++;
	break;
      }
    }

    if (i >= BUFSIZ) {
      temptext[BUFSIZ - 1] = '\000'; /* null terminate end of temptext[] */
      fprintf(stderr,
	      "HTML Parser Warning: truncated input line larger than %d characters ... line='%s'\n",
	      BUFSIZ,
	      temptext);
    }
    else {
      *cp = '\000';		/* null terminate temptext[] */
    }

    if ((temptext[0] != '\000')
	|| ((temptext[0] == '\000')
	    && (*wp == '\n'))	/* handle case of input being "\n\n...\n" */
	/* this means we are creating a textline object with an empty string */
	) {
      if (o_prev != NIL)
	setivar(o_prev, WORDOBJ_NEXT_WORDOBJ, o_last);
      setivar(o_last, WORDOBJ_PREV_WORDOBJ, o_prev);
      setivar(o_last, WORDOBJ_STRING, 
	      cvstring(temptext)); /* cvstring() allocates a new string here, it is protected from GC because o_last is */
      setivar(o_last, WORDOBJ_NEWLINEP,
	      (*wp == '\n') ? s_true : NIL);

      /* setivar(o_last, WORDOBJ_TAGOBJ, NIL); *//* this now gets set in Htp_Create_HTML_Tag_Inst() called out of Htp_Dump_Tree() */
      o_prev = o_last;
      o_last =
	newobject(o_HTML_TEXTLINE_IMAGE_CLASS, /* init'd in 'HTML:INIT_PARSER' */
		  getivcnt(o_HTML_TEXTLINE_IMAGE_CLASS, IVARTOTAL));
    }
    else {
      o_last = NIL;		/* note: this wastes the last newobject()
				   created, it'll get garbage collected
				   eventually */
    }
  } while (o_last != NIL);

  xlpopn(2);
  return (o_word);
}

static LVAL
Htp_Create_HTML_Tag_Inst(LVAL tag_cls,
			 LVAL tag_sym,
			 LVAL tag_parent,
			 LVAL tag_data,
			 LVAL tag_attrs,
			 LVAL tag_flags)
{
  LVAL o_tag;
  xlsave1(o_tag);		/* protect this pointer from GC */

  if ((tag_cls == NIL) || (tag_sym == NIL))
    xlerror("Invalid call to Htp_Create_HTML_Tag_Inst()", tag_sym);

  o_tag = newobject(tag_cls, getivcnt(tag_cls, IVARTOTAL));
  setivar(o_tag, TAGOBJ_CHILDREN,	NIL);
  setivar(o_tag, TAGOBJ_LAST_CHILD,	NIL);
  setivar(o_tag, TAGOBJ_PARENT,		tag_parent);
  setivar(o_tag, TAGOBJ_NAME,		tag_sym);
  setivar(o_tag, TAGOBJ_DATA,		tag_data);
  setivar(o_tag, TAGOBJ_ATTRS,		tag_attrs);
  setivar(o_tag, TAGOBJ_FLAGS,		tag_flags);

  if ((ntype(tag_data) == OBJECT)
      && ((getclass(tag_data) == o_HTML_TEXTWORD_IMAGE_CLASS)
	  || (getclass(tag_data) == o_HTML_TEXTLINE_IMAGE_CLASS))) {
    while (tag_data != NIL) {
      setivar(tag_data, WORDOBJ_TAGOBJ, o_tag); /* set backpointer from tangoimage to the "tag" */
      tag_data = getivar(tag_data, WORDOBJ_NEXT_WORDOBJ); /* doit for all words in this "tag" */
      /* ^^^^^ -- note that tag_data got modified here -- don't use tag_data after this point... */
    }
  }

  xlpop();			/* no longer need to protect o_tag from GC */
  return (o_tag);
}


/******************************************************************************
 * (HTML:PARSE_FILE <html_filepath>)
 *	--> returns the toplevel document object representing the parse tree.
 * Atter this function executes, the file "/usr/tmp/htmlparse.err"
 * (see HTML_PARSER_ERROR_FILEPATH for default value)
 * will contain the error messages associated with parsing <html_filepath>.
 * The name of the file is obtained from symbol HTML::*HTML_PARSE_ERROR_FILEPATH*
 * the user may retrieve the current filepath, or set the filepath to be used
 * in future calls to HTML:PARSE_FILE.
******************************************************************************/
LVAL
Prim_HTML_PARSE_FILE()
{
  LVAL		result;
  LVAL		lval_filepath;
  char* 	filepath;
  LVAL		lval_err_filepath;
  char* 	err_filepath;
  Block*	bp;
#ifdef PACKAGES
  LVAL		oldpack;
#endif /* PACKAGES */


  /* get file to parse */
  lval_filepath = xlgastring();
  filepath = getstring(lval_filepath);
  xllastarg();

  if ((o_HTML_DOCUMENT_CLASS == NULL)
      || (o_HTML_COMMENT_CLASS == NULL)
      || (o_HTML_DOCTYPE_CLASS == NULL)
      || (o_HTML_TEXT_CLASS == NULL)
      || (o_HTML_TEXTWORD_IMAGE_CLASS == NULL)
      || (o_HTML_TEXTLINE_IMAGE_CLASS == NULL)
      || (o_HTML_DYNAMIC_DATE_CLASS == NULL)
      || (o_HTML_DYNAMIC_EMAIL_ADDRESS_CLASS == NULL)
      || (o_HTML_DYNAMIC_EXEC_CLASS == NULL)
      || (o_HTML_DYNAMIC_USER_CLASS == NULL)
      || (o_HTML_UNKNOWN_TYPE_CLASS == NULL)
      )
    xlfail("must call HTML:INIT_PARSER prior to calling HTML:PARSE_FILE");

#ifdef PACKAGES
  oldpack = getvalue(s_package);
  if (xlhtmlpack == NULL)
    xlfail("Fatal error in HTML:PARSE_FILE -- package 'HTML' not initialized.");
  setvalue(s_package, xlhtmlpack);
#endif /* PACKAGES */

  lval_err_filepath = getvalue(s_HTML_PARSE_ERROR_FILEPATH);
  if (stringp(lval_err_filepath)
      && (getslength(lval_err_filepath) > 0))
    err_filepath = getstring(lval_err_filepath);
  else
    err_filepath = HTML_PARSER_ERROR_FILEPATH;

  if (filepath
      && (strlen(filepath) > 0)
      && (bp = Htp_Parse_Tree_From_HTML_File(filepath, err_filepath)) /* if error, err_buf is set */
      ) {
    xlsave1(result);		/* protect this pointer from GC */
    result = Htp_Create_HTML_Tag_Inst(o_HTML_DOCUMENT_CLASS, /* tag_cls */
				      s_HTML_DOCUMENT, /* tag_sym */
				      NIL, /* tag_parent */
				      lval_filepath, /* tag_data */
				      NIL, /* tag_attrs */
				      NIL); /* tag_flags */
    add_leading_whitespace_on_next_word_p = FALSE;
    Htp_Dump_Tree(bp, 0, result, FALSE);
    HTMLFreeParseTree(bp);
    xlpop();			/* no longer need to protect 'result' from GC */
#ifdef PACKAGES
    setvalue(s_package, oldpack);
#endif /* PACKAGES */
    return (result);
  }
  else if (strlen(err_buf) > 0) {
#ifdef PACKAGES
    setvalue(s_package, oldpack);
#endif /* PACKAGES */
    xlfail(err_buf);
  }
  else {
#ifdef PACKAGES
    setvalue(s_package, oldpack);
#endif /* PACKAGES */
    return (NIL);
  }
}


typedef struct _HTMLtag_Extension {
  LVAL        HTMLtag_CLS;	/* the Class object for the tag */
  LVAL        HTMLtag_SYM;	/* the symbol name */
} HTMLtag_Extension;


#define HTMLTAG_TABLE_SIZE (MAXATOM + 1)	/* need to increase this if we add more HTMLtages */
static HTMLtag_Extension HTMLtag_table[HTMLTAG_TABLE_SIZE];

static void
Htp_Setup_Atom_Table_Elt(char *string, LVAL tag_class)
{
  int idx = (int) GetAtom(string);

  if (idx > MAXATOM)
    xlfail("Fatal error in HTML:INIT_PARSER -- recompile with larger HTMLTAG_TABLE_SIZE");

  HTMLtag_table[idx].HTMLtag_CLS = tag_class;
  HTMLtag_table[idx].HTMLtag_SYM = xlenter(string);
}

static LVAL
Htp_Get_Class_From_Symname(char *string)
{
  LVAL cls_sym = xlenter(string);
  LVAL cls_obj = getvalue(cls_sym);
  
  if ((ntype(cls_obj) == OBJECT)
      && (getclass(cls_obj) == cls_class))
    return (cls_obj);
  else if (cls_obj == s_unbound)
    xlerror("in HTML:INIT_PARSER, HTML-*-CLASS class-objects must be defined before calling", cls_sym);
  else
    xlerror("in HTML:INIT_PARSER, invalid HTML-*-CLASS object", cls_obj);
}

static LVAL
Htp_Attrs_to_List(struct attribute *ap)
{
  LVAL result, att_pair, att_name, att_val, r_tail;

  xlstkcheck(4);
  xlsave(result);		/* protect this pointer from GC */
  xlsave(att_pair);		/* protect this pointer from GC */
  xlsave(att_name);		/* protect this pointer from GC */
  xlsave(att_val);		/* protect this pointer from GC */

  result = NIL;

  /* output attributes */
  while (ap) {
    int idx = (int) ap->aAtom;
    /* fprintf(stdout, "att-name=%s\n", ap->name); */
    if ((idx >= 0) && (idx <= MAXATOM)
	&& ((att_name = HTMLtag_table[idx].HTMLtag_SYM) != NIL)
	&& (ntype(att_name) == SYMBOL)) {
      /* no-op -- att_name set above */
    }
    else if (ap->name && (ap->name[0] != '\000')) {
      char* inp = &(ap->name[0]);
      char* cp = &temptext[0];
      /* convert to upper-case before calling xlenter() */
      while (*inp)
	*cp++ = toupper(*inp++);
      *cp = '\0';
      att_name = xlenter(temptext);
    }
    else {
      att_name = NIL;
    }

    if (!((idx >= 0) && (idx <= MAXATOM)))
      fprintf(stderr,
	      "Internal error in HTML parser Htp_Attrs_to_List()\n\t-- index returned by ap->aAtom smaller than 0 or larger than %d -- %d ... data='%s'\n",
	      MAXATOM,
	      idx,
	      ap->data
	      );

    if (ap->data) 		/* for now, ignore conditions like
				   (ap->flags & DQUOTEDVALUE) ==> "=\"%s\""
				   (ap->flags & SQUOTEDVALUE) ==> "='%s'"
				   because we can just normalize this data
				   in the output routine */
      att_val = cvstring(ap->data);
    else
      att_val = NIL;

    att_pair = cons(att_name, att_val);

    /* add 'att_pair' to the end of the list of attributes */
    if (NIL == result)
      result = r_tail = cons(att_pair, NIL); /* point the head/tail of the children list to it */
    else {
      LVAL new_elt = cons(att_pair, NIL); /* create new tail elt */
      rplacd(r_tail, new_elt);	/* add the new tail elt to the last child */
      r_tail = new_elt;		/* make the new elt be the last child */
    }

    ap = ap->next;
  }
  xlpopn(4);			/* no longer need to protect 'result' from GC */
  return (result);
}


static void
Htp_Dump_Tree(Block *top, int depth, LVAL parent_node, Boolean split_text_at_line_p)
{
  extern atom atomTITLE;	/* init'd by HTMLParseBegin(), called from winterp_embedded_init_xlisp_objs() */
  extern atom atomPRE;		/* init'd by HTMLParseBegin(), called from winterp_embedded_init_xlisp_objs() */
  Block *cur;
  LVAL result;
  LVAL tag_sym, tag_cls, tag_data, tag_attrs, tag_flags;
  int idx;
  Boolean ignore_these_children_p = FALSE;

  xlstkcheck(4);
  xlsave(result);		/* protect this pointer from GC */
  xlsave(tag_data);		/* protect this pointer from GC */
  xlsave(tag_attrs);		/* protect this pointer from GC */
  xlsave(tag_flags);		/* protect this pointer from GC */

  /* Print out block data */
  if (depth != 0) { 		/* Ignore top block data */
    switch (top->type) {
    case DOCTYPE:
      tag_cls   = o_HTML_DOCTYPE_CLASS;
      tag_sym   = s_HTML_DOCTYPE;
      tag_data  = top->data ? cvstring(top->data) : NIL;
      tag_attrs = top->attrs ? Htp_Attrs_to_List(top->attrs) : NIL;
      tag_flags = top->flags ? cvfixnum((FIXTYPE) top->flags) : NIL;
      break;
    case CMNT:
      tag_cls   = o_HTML_COMMENT_CLASS;
      tag_sym   = s_HTML_COMMENT;
      tag_data  = top->data ? cvstring(top->data) : NIL;
      tag_attrs = top->attrs ? Htp_Attrs_to_List(top->attrs) : NIL;
      tag_flags = top->flags ? cvfixnum((FIXTYPE) top->flags) : NIL;
      break;
    case PCDATA:
      tag_cls   = o_HTML_TEXT_CLASS;
      tag_sym   = s_HTML_TEXT;

      if (top->data && (*(top->data) != '\000')) {
	if (split_text_at_line_p)
	  tag_data = Htp_Make_Text_Line_List(top->data);
	else
	  tag_data = Htp_Make_Text_Word_List(top->data);
      }
      else
	tag_data = NIL;

      tag_attrs = top->attrs ? Htp_Attrs_to_List(top->attrs) : NIL;
      tag_flags = top->flags ? cvfixnum((FIXTYPE) top->flags) : NIL;
      break;
    case TAG:
      idx = (int) HTMLGetBAtom(top);
      if (idx == (int) atomTITLE) { /* special case for <TITLE> tag -- 
				       make the TEXT child's data cur->data */
	tag_data = NIL;
	/*
	 * NPM: the title text is "under" the title node, remove that node
	 * from the parse tree and make it the data for the title node
	 */
	cur = top->children;
	if (cur) {
	  if (cur->type == PCDATA)
	    tag_data = cur->data ? cvstring(cur->data) : NIL;
	  /*
	   * NPM: now that the TITLE node contains the title text from the
	   * TEXT child, prevent a recursive call Htp_Dump_Tree() below
	   */
	  ignore_these_children_p = TRUE;
	}

	tag_cls   = HTMLtag_table[idx].HTMLtag_CLS;
	tag_sym   = HTMLtag_table[idx].HTMLtag_SYM;
	if (tag_data == NIL)
	  tag_data = cvstring("");
	tag_attrs = top->attrs ? Htp_Attrs_to_List(top->attrs) : NIL;
	tag_flags = top->flags ? cvfixnum((FIXTYPE) top->flags) : NIL;
      }
      else if (idx == (int) atomPRE) { /* special case for <PRE> -- set 'split_text_at_line_p' */
	split_text_at_line_p = TRUE; /* see call to Htp_Make_Text_Line_List() or Htp_Make_Text_Word_List() above, plus recursion below */
	tag_cls   = HTMLtag_table[idx].HTMLtag_CLS;
	tag_sym   = HTMLtag_table[idx].HTMLtag_SYM;
	tag_data  = NIL;
	tag_attrs = top->attrs ? Htp_Attrs_to_List(top->attrs) : NIL;
	tag_flags = top->flags ? cvfixnum((FIXTYPE) top->flags) : NIL;
      }
      else if ((idx >= 0) && (idx <= MAXATOM)
	       && ((tag_cls = HTMLtag_table[idx].HTMLtag_CLS) != NIL)
	       && (ntype(tag_cls) == OBJECT)
	       && (getclass(tag_cls) == cls_class)) {
	/* if the tag/atom has a known class, use it
	   otherwise we use html-unknown-class below ...*/
	tag_sym   = HTMLtag_table[idx].HTMLtag_SYM;
	tag_data  = NIL;
	tag_attrs = top->attrs ? Htp_Attrs_to_List(top->attrs) : NIL;
	tag_flags = top->flags ? cvfixnum((FIXTYPE) top->flags) : NIL;
      }
      else {			/* markup is of an unknown class */
	if (!((idx >= 0) && (idx <= MAXATOM)))
	  fprintf(stderr,
		  "Internal error in HTML parser Htp_Dump_Tree()\n\t-- index returned by HTMLGetBAtom() smaller than 0 or larger than %d -- %d ... data='%s'\n",
		  MAXATOM,
		  idx,
		  top->data);

	if (top->data && (top->data[0] != '\000')) {
	  char* inp = &(top->data[0]);
	  char* cp = &temptext[0];
	  /* convert to upper-case before calling xlenter() */
	  while (*inp)
	    *cp++ = toupper(*inp++);
	  *cp = '\0';
	  tag_sym = xlenter(temptext);
	}
	else {
	  tag_sym = s_UNKNOWN;
	}

	if (tag_sym == s_DYNAMIC_DATE)
	  tag_cls = o_HTML_DYNAMIC_DATE_CLASS;
	else if (tag_sym == s_DYNAMIC_EMAIL)
	  tag_cls = o_HTML_DYNAMIC_EMAIL_ADDRESS_CLASS;
	else if (tag_sym == s_DYNAMIC_EXEC)
	  tag_cls = o_HTML_DYNAMIC_EXEC_CLASS;
	else if (tag_sym == s_DYNAMIC_USER)
	  tag_cls = o_HTML_DYNAMIC_USER_CLASS;
	else 
	  tag_cls = o_HTML_UNKNOWN_TYPE_CLASS;

	tag_data  = NIL;
	tag_attrs = top->attrs ? Htp_Attrs_to_List(top->attrs) : NIL;
	tag_flags = top->flags ? cvfixnum((FIXTYPE) top->flags) : NIL;
      }
      break;
    default:
      fprintf(stderr, "Internal error in HTML parser Htp_Dump_Tree()\n\t-- unknown value for top->type\n");
      tag_cls   = o_HTML_UNKNOWN_TYPE_CLASS;
      tag_sym   = s_UNKNOWN;
      tag_data  = NIL;
      tag_attrs = NIL;
      tag_flags = NIL;
      break;
    }

    /*
     * Note: if 'tag_data' is a WordObject (see Htp_Make_Text_Line_List() or
     * Htp_Make_Text_Word_List() usage above) then a back-pointer from the
     * WordObject to the TagObject ('result') is set for each word object in
     * call to Htp_Create_HTML_Tag_Inst().
     */
    result =
      Htp_Create_HTML_Tag_Inst(tag_cls, tag_sym, parent_node,
			       tag_data, tag_attrs, tag_flags);

    /* add 'result' to the parent's child list */
    if (NIL == getivar(parent_node, TAGOBJ_CHILDREN)) {
      LVAL new_elt = cons(result, NIL); /* create a new elt */
      setivar(parent_node, TAGOBJ_CHILDREN, new_elt); /* point the head of the children list to it */
      setivar(parent_node, TAGOBJ_LAST_CHILD, new_elt);	/* point the tail of the children list to it too */
    }
    else {
      LVAL new_elt = cons(result, NIL); /* create new tail elt */
      rplacd(getivar(parent_node, TAGOBJ_LAST_CHILD), new_elt); /* add the new tail elt to the last child */
      setivar(parent_node, TAGOBJ_LAST_CHILD, new_elt);	/* make the new elt be the last child */
    }
  }
  else {
    /* skip the current node at level==0, don't add any children, the
       recursive call to Htp_Dump_Tree() below will add the children to
       result==parent_node==the node created in the top-level call to
       Htp_Dump_tree() in 'Prim_HTML_PARSE_FILE'. */
    result = parent_node;
  }

  /*
   * no longer need to protect 'result' from GC. Note that this is done in
   * this case *before* the call to Htp_Dump_Tree() so that a deep dump
   * don't overflow any stacks. Note however that 'result' is actually
   * protected from GC by virtue of the above code which adds 'result' as a
   * child of 'parent_node'. By virtue of the toplevel 'document' parent
   * node being saved on the top-level call to this proc, all child results
   * are saved as well.
   */
  xlpopn(4);

  if ((top->type == TAG) && (top->flags & PRETTYSTART))
    add_leading_whitespace_on_next_word_p = TRUE;

  if (!ignore_these_children_p) {
    /* recurse over children */
    cur = top->children;
    while (cur) {
      Htp_Dump_Tree(cur, depth + 1, result, split_text_at_line_p);
      cur = cur->next_sib;
    }
  }

  if ((top->type == TAG) && (top->flags & PRETTYEND))
    add_leading_whitespace_on_next_word_p = TRUE;
}


/******************************************************************************
 * (HTML:INIT_PARSER)
 *	--> returns NIL
******************************************************************************/
LVAL
Prim_HTML_INIT_PARSER ()
{
  int idx;
  LVAL o_HTML_ADDRESS_CLASS;
  LVAL o_HTML_ANCHOR_CLASS;
  LVAL o_HTML_BODY_CLASS;
  LVAL o_HTML_BREAK_CLASS;
  LVAL o_HTML_DEFINITION_DEFINITION_CLASS;
  LVAL o_HTML_DEFINITION_LIST_CLASS;
  LVAL o_HTML_DEFINITION_TERM_CLASS;
  LVAL o_HTML_EMPHASIS_CLASS;
  LVAL o_HTML_FORM_CLASS;
  LVAL o_HTML_HEAD_CLASS;
  LVAL o_HTML_HEADER_CLASS;
  LVAL o_HTML_HORIZONTAL_RULE_CLASS;
  LVAL o_HTML_HTML_CLASS;
  LVAL o_HTML_IMAGE_CLASS;
  LVAL o_HTML_IN_HEAD_CLASS;
  LVAL o_HTML_INPUT_CLASS;
  LVAL o_HTML_LIST_CLASS;
  LVAL o_HTML_LIST_ITEM_CLASS;
  LVAL o_HTML_OPTION_CLASS;
  LVAL o_HTML_PARAGRAPH_CLASS;
  LVAL o_HTML_PRE_CLASS;
  LVAL o_HTML_QUOTE_CLASS;
  LVAL o_HTML_SELECT_CLASS;
  LVAL o_HTML_TEXTAREA_CLASS;
  LVAL o_HTML_TITLE_CLASS;

#ifdef PACKAGES
  LVAL oldpack = getvalue(s_package);
  if (xlhtmlpack == NULL)
    xlfail("Fatal error in HTML:INIT_PARSER -- package 'HTML' not initialized.");
/*
  else
    fprintf(stdout, "package 'HTML' was set\n");
*/
  setvalue(s_package, xlhtmlpack);
#endif /* PACKAGES */

  o_HTML_DOCUMENT_CLASS = Htp_Get_Class_From_Symname("DOCUMENT-CLASS");

  /* Fetch all the HTML markup classes that are defined in Winterp-Lisp */
  o_HTML_ADDRESS_CLASS = Htp_Get_Class_From_Symname("ADDRESS-CLASS");
  o_HTML_ANCHOR_CLASS = Htp_Get_Class_From_Symname("ANCHOR-CLASS");
  o_HTML_BODY_CLASS = Htp_Get_Class_From_Symname("BODY-CLASS");
  o_HTML_BREAK_CLASS = Htp_Get_Class_From_Symname("BREAK-CLASS");
  o_HTML_COMMENT_CLASS = Htp_Get_Class_From_Symname("COMMENT-CLASS");
  o_HTML_DEFINITION_DEFINITION_CLASS = Htp_Get_Class_From_Symname("DEFINITION-DEFINITION-CLASS");
  o_HTML_DEFINITION_LIST_CLASS = Htp_Get_Class_From_Symname("DEFINITION-LIST-CLASS");
  o_HTML_DEFINITION_TERM_CLASS = Htp_Get_Class_From_Symname("DEFINITION-TERM-CLASS");
  o_HTML_DOCTYPE_CLASS = Htp_Get_Class_From_Symname("DOCTYPE-CLASS");
  o_HTML_DYNAMIC_DATE_CLASS = Htp_Get_Class_From_Symname("DYNAMIC-DATE-CLASS");
  o_HTML_DYNAMIC_EMAIL_ADDRESS_CLASS = Htp_Get_Class_From_Symname("DYNAMIC-EMAIL-ADDRESS-CLASS");
  o_HTML_DYNAMIC_EXEC_CLASS = Htp_Get_Class_From_Symname("DYNAMIC-EXEC-CLASS");
  o_HTML_DYNAMIC_USER_CLASS = Htp_Get_Class_From_Symname("DYNAMIC-USER-CLASS");
  o_HTML_EMPHASIS_CLASS = Htp_Get_Class_From_Symname("EMPHASIS-CLASS");
  o_HTML_FORM_CLASS = Htp_Get_Class_From_Symname("FORM-CLASS");
  o_HTML_HEAD_CLASS = Htp_Get_Class_From_Symname("HEAD-CLASS");
  o_HTML_HEADER_CLASS = Htp_Get_Class_From_Symname("HEADER-CLASS");
  o_HTML_HORIZONTAL_RULE_CLASS = Htp_Get_Class_From_Symname("HORIZONTAL-RULE-CLASS");
  o_HTML_HTML_CLASS = Htp_Get_Class_From_Symname("HTML-CLASS");
  o_HTML_IMAGE_CLASS = Htp_Get_Class_From_Symname("IMAGE-CLASS");
  o_HTML_IN_HEAD_CLASS = Htp_Get_Class_From_Symname("IN-HEAD-CLASS");
  o_HTML_INPUT_CLASS = Htp_Get_Class_From_Symname("INPUT-CLASS");
  o_HTML_LIST_CLASS = Htp_Get_Class_From_Symname("LIST-CLASS");
  o_HTML_LIST_ITEM_CLASS = Htp_Get_Class_From_Symname("LIST-ITEM-CLASS");
  o_HTML_OPTION_CLASS = Htp_Get_Class_From_Symname("OPTION-CLASS");
  o_HTML_PARAGRAPH_CLASS = Htp_Get_Class_From_Symname("PARAGRAPH-CLASS");
  o_HTML_PRE_CLASS = Htp_Get_Class_From_Symname("PRE-CLASS");
  o_HTML_QUOTE_CLASS = Htp_Get_Class_From_Symname("QUOTE-CLASS");
  o_HTML_SELECT_CLASS = Htp_Get_Class_From_Symname("SELECT-CLASS");
  o_HTML_TEXT_CLASS = Htp_Get_Class_From_Symname("TEXT-CLASS");
  o_HTML_TEXTAREA_CLASS = Htp_Get_Class_From_Symname("TEXTAREA-CLASS");
  o_HTML_TITLE_CLASS = Htp_Get_Class_From_Symname("TITLE-CLASS");
  o_HTML_UNKNOWN_TYPE_CLASS = Htp_Get_Class_From_Symname("UNKNOWN-TYPE-CLASS");
  o_HTML_TEXTWORD_IMAGE_CLASS = Htp_Get_Class_From_Symname("TEXTWORD-IMAGE-CLASS");
  o_HTML_TEXTLINE_IMAGE_CLASS = Htp_Get_Class_From_Symname("TEXTLINE-IMAGE-CLASS");

  s_HTML_PARSE_ERROR_FILEPATH = xlenter("*HTML_PARSE_ERROR_FILEPATH*");
  setvalue(s_HTML_PARSE_ERROR_FILEPATH, cvstring(HTML_PARSER_ERROR_FILEPATH)); 

  s_HTML_COMMENT	= xlenter("!--");
  s_HTML_TEXT		= xlenter("TEXT");
  s_HTML_DOCTYPE	= xlenter("!DOCTYPE");
  s_HTML_DOCUMENT	= xlenter("DOCUMENT");
  s_DYNAMIC_DATE	= xlenter("D-DATE");
  s_DYNAMIC_EMAIL	= xlenter("D-EMAIL-ADDR");
  s_DYNAMIC_EXEC	= xlenter("D-EXEC");
  s_DYNAMIC_USER	= xlenter("D-USER");
  s_UNKNOWN		= xlenter("???");
  
  /* clear out all entries s.t. after all the Htp_Setup_Atom_Table_Elt()
     calls are done, any "skipped" or "missing" atoms (ones not set up
     below) have NIL values. */
  for (idx = 0; (idx < HTMLTAG_TABLE_SIZE); idx++) {
    HTMLtag_table[idx].HTMLtag_CLS = NIL;
    HTMLtag_table[idx].HTMLtag_SYM = NIL;
  }
  
  /* the sequence of elements here is derived from
     jays-parser/dtd/libdtd/dtddefs.h, but since that include file
     can't/shouldn't be included here in any useful way, I'm building up
     the table by calls to GetAtom(). The point of all this is to get a
     table, which when accessed via an 'atom' index from the parser, will
     return appropriate information to create a WINTERP object with the
     appropriate class, and with data initialized to the appropriate values
     (e.g.  the tag name symbol, etc.)
     */

  /* Htp_Setup_Atom_Table_Elt(0,	NIL); */
  Htp_Setup_Atom_Table_Elt("TT",		o_HTML_EMPHASIS_CLASS);
  Htp_Setup_Atom_Table_Elt("B",		o_HTML_EMPHASIS_CLASS);
  Htp_Setup_Atom_Table_Elt("I",		o_HTML_EMPHASIS_CLASS);
  Htp_Setup_Atom_Table_Elt("EM",		o_HTML_EMPHASIS_CLASS);
  Htp_Setup_Atom_Table_Elt("STRONG",	o_HTML_EMPHASIS_CLASS);
  Htp_Setup_Atom_Table_Elt("CODE",	o_HTML_EMPHASIS_CLASS);
  Htp_Setup_Atom_Table_Elt("SAMP",	o_HTML_EMPHASIS_CLASS);
  Htp_Setup_Atom_Table_Elt("KBD",	o_HTML_EMPHASIS_CLASS);
  Htp_Setup_Atom_Table_Elt("VAR",	o_HTML_EMPHASIS_CLASS);
  Htp_Setup_Atom_Table_Elt("CITE",	o_HTML_EMPHASIS_CLASS);
  Htp_Setup_Atom_Table_Elt("PCDATA",	o_HTML_TEXT_CLASS);
  Htp_Setup_Atom_Table_Elt("A",		o_HTML_ANCHOR_CLASS);
  Htp_Setup_Atom_Table_Elt("IMG",	o_HTML_IMAGE_CLASS);
  Htp_Setup_Atom_Table_Elt("BR",		o_HTML_BREAK_CLASS);
  Htp_Setup_Atom_Table_Elt("SDAFORM",	NIL); /* ??? */
  Htp_Setup_Atom_Table_Elt("CDATA",	NIL); /* ??? */
  Htp_Setup_Atom_Table_Elt("SDAPREF",	NIL); /* ??? */
  Htp_Setup_Atom_Table_Elt("H1",		o_HTML_HEADER_CLASS);
  Htp_Setup_Atom_Table_Elt("H2",		o_HTML_HEADER_CLASS);
  Htp_Setup_Atom_Table_Elt("H3",		o_HTML_HEADER_CLASS);
  Htp_Setup_Atom_Table_Elt("H4",		o_HTML_HEADER_CLASS);
  Htp_Setup_Atom_Table_Elt("H5",		o_HTML_HEADER_CLASS);
  Htp_Setup_Atom_Table_Elt("H6",		o_HTML_HEADER_CLASS);
  Htp_Setup_Atom_Table_Elt("HREF",	NIL);
  Htp_Setup_Atom_Table_Elt("NAME",	NIL);
  Htp_Setup_Atom_Table_Elt("REL",	NIL);
  Htp_Setup_Atom_Table_Elt("REV",	NIL);
  Htp_Setup_Atom_Table_Elt("URN",	NIL);
  Htp_Setup_Atom_Table_Elt("TITLE",	o_HTML_TITLE_CLASS);
  Htp_Setup_Atom_Table_Elt("METHODS",	NIL);
  Htp_Setup_Atom_Table_Elt("NAMES",	NIL);
  Htp_Setup_Atom_Table_Elt("SRC",	NIL);
  Htp_Setup_Atom_Table_Elt("ALT",	NIL);
  Htp_Setup_Atom_Table_Elt("TOP",	NIL);
  Htp_Setup_Atom_Table_Elt("MIDDLE",	NIL);
  Htp_Setup_Atom_Table_Elt("BOTTOM",	NIL);
  Htp_Setup_Atom_Table_Elt("ALIGN",	NIL);
  Htp_Setup_Atom_Table_Elt("ISMAP",	NIL);
  Htp_Setup_Atom_Table_Elt("P",		o_HTML_PARAGRAPH_CLASS);
  Htp_Setup_Atom_Table_Elt("HR",		o_HTML_HORIZONTAL_RULE_CLASS);
  Htp_Setup_Atom_Table_Elt("PRE",	o_HTML_PRE_CLASS);
  Htp_Setup_Atom_Table_Elt("WIDTH",	NIL);
  Htp_Setup_Atom_Table_Elt("NUMBER",	NIL);
  Htp_Setup_Atom_Table_Elt("XMP",	o_HTML_UNKNOWN_TYPE_CLASS); /* "Deprecated" in 2.0 DTD */
  Htp_Setup_Atom_Table_Elt("LISTING",	o_HTML_UNKNOWN_TYPE_CLASS); /* "Deprecated" in 2.0 DTD */
  Htp_Setup_Atom_Table_Elt("PLAINTEXT",	o_HTML_UNKNOWN_TYPE_CLASS); /* "Deprecated" in 2.0 DTD */
  Htp_Setup_Atom_Table_Elt("DL",		o_HTML_DEFINITION_LIST_CLASS);
  Htp_Setup_Atom_Table_Elt("DT",		o_HTML_DEFINITION_TERM_CLASS);
  Htp_Setup_Atom_Table_Elt("DD",		o_HTML_DEFINITION_DEFINITION_CLASS);
  Htp_Setup_Atom_Table_Elt("COMPACT",	NIL);
  Htp_Setup_Atom_Table_Elt("UL",		o_HTML_LIST_CLASS);
  Htp_Setup_Atom_Table_Elt("OL",		o_HTML_LIST_CLASS);
  Htp_Setup_Atom_Table_Elt("DIR",	o_HTML_LIST_CLASS);
  Htp_Setup_Atom_Table_Elt("MENU",	o_HTML_LIST_CLASS);
  Htp_Setup_Atom_Table_Elt("BLOCKQUOTE",	o_HTML_QUOTE_CLASS);
  Htp_Setup_Atom_Table_Elt("FORM",	o_HTML_FORM_CLASS);
  Htp_Setup_Atom_Table_Elt("ISINDEX",	o_HTML_IN_HEAD_CLASS);
  Htp_Setup_Atom_Table_Elt("LI",		o_HTML_LIST_ITEM_CLASS);
  Htp_Setup_Atom_Table_Elt("BODY",	o_HTML_BODY_CLASS);
  Htp_Setup_Atom_Table_Elt("ADDRESS",	o_HTML_ADDRESS_CLASS);
  Htp_Setup_Atom_Table_Elt("INPUT",	o_HTML_INPUT_CLASS);
  Htp_Setup_Atom_Table_Elt("SELECT",	o_HTML_SELECT_CLASS);
  Htp_Setup_Atom_Table_Elt("TEXTAREA",	o_HTML_TEXTAREA_CLASS);
  Htp_Setup_Atom_Table_Elt("ACTION",	NIL);
  Htp_Setup_Atom_Table_Elt("GET",	NIL);
  Htp_Setup_Atom_Table_Elt("POST",	NIL);
  Htp_Setup_Atom_Table_Elt("METHOD",	NIL);
  Htp_Setup_Atom_Table_Elt("ENCTYPE",	NIL);
  Htp_Setup_Atom_Table_Elt("SDASUFF",	NIL);
  Htp_Setup_Atom_Table_Elt("TEXT",	NIL);
  Htp_Setup_Atom_Table_Elt("PASSWORD",	NIL);
  Htp_Setup_Atom_Table_Elt("CHECKBOX",	NIL);
  Htp_Setup_Atom_Table_Elt("RADIO",	NIL);
  Htp_Setup_Atom_Table_Elt("SUBMIT",	NIL);
  Htp_Setup_Atom_Table_Elt("RESET",	NIL);
  Htp_Setup_Atom_Table_Elt("IMAGE",	NIL);
  Htp_Setup_Atom_Table_Elt("HIDDEN",	NIL);
  Htp_Setup_Atom_Table_Elt("TYPE",	NIL);
  Htp_Setup_Atom_Table_Elt("VALUE",	NIL);
  Htp_Setup_Atom_Table_Elt("CHECKED",	NIL);
  Htp_Setup_Atom_Table_Elt("SIZE",	NIL);
  Htp_Setup_Atom_Table_Elt("MAXLENGTH",	NIL);
  Htp_Setup_Atom_Table_Elt("OPTION",	o_HTML_OPTION_CLASS);
  Htp_Setup_Atom_Table_Elt("MULTIPLE",	NIL);
  Htp_Setup_Atom_Table_Elt("SELECTED",	NIL);
  Htp_Setup_Atom_Table_Elt("ROWS",	NIL);
  Htp_Setup_Atom_Table_Elt("COLS",	NIL);
  Htp_Setup_Atom_Table_Elt("HEAD",	o_HTML_HEAD_CLASS);
  Htp_Setup_Atom_Table_Elt("BASE",	o_HTML_IN_HEAD_CLASS);
  Htp_Setup_Atom_Table_Elt("NEXTID",	o_HTML_IN_HEAD_CLASS);
  Htp_Setup_Atom_Table_Elt("META",	o_HTML_IN_HEAD_CLASS);
  Htp_Setup_Atom_Table_Elt("LINK",	o_HTML_IN_HEAD_CLASS);
  Htp_Setup_Atom_Table_Elt("N",		NIL);
  Htp_Setup_Atom_Table_Elt("HTTP_EQUIV",	NIL);
  Htp_Setup_Atom_Table_Elt("CONTENT",	NIL);
  Htp_Setup_Atom_Table_Elt("HTML",	o_HTML_HTML_CLASS);
  Htp_Setup_Atom_Table_Elt("VERSION",	NIL);
  Htp_Setup_Atom_Table_Elt("MAXELEMENT",NIL);

#ifdef PACKAGES
  setvalue(s_package, oldpack);
#endif /* PACKAGES */
  return (NIL);
}
